#include "task.hpp"

class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string &name)
        : _wfd(wfd), _subprocessid(id), _name(name)
    {
    }

    int getwfd() { return _wfd; }
    pid_t getprocessid() { return _subprocessid; }
    std::string getname() { return _name; }
    void closeChannel()
    {
        close(_wfd);
    }
    void wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait " << rid << " success" << std::endl;
        }
    }
    ~Channel()
    {
    }

private:
    int _wfd;
    pid_t _subprocessid;
    std::string _name;
};

// 形参类型 : 命名  规范
//  const & : 输出
//  &       : 输入输出型参数
//  *       : 输出型参数
void createchannelandsub(int num, std::vector<Channel> *channels, task_t task)
{
    //
    for (int i = 0; i < num; i++)
    {
        // 1. 利用pipe先创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if (n < 0)
        {
            exit(1);
        }

        //  2. 创建子进程
        // 每次循环调用一次fork(),而且用一个if(fork() == 0){}判断
        // 在代码块中执行完当前子进程需要完成的任务之后，结束当前子进程
        
        pid_t id = fork();
        if (id == 0)
        {
            //如果用来管理 管道的文件 的 channels 这个 vector 容器不为空，
            //那么就会发生在cleanupChannel这个函数中所说的    多个写入指针你指向同一个管道   这一现象
            //那么满足判断条件，立马就执行下面的循环，关掉当前的子进程
            if (!channels->empty())
            {
                for(auto &channel : *channels)
                {
                    channel.closeChannel();
                }
            }
            

            // 继承体系中创建子进程之后第一件事是把不需要的文件描述符对应的文件关闭
            // 以确定读写权限，这里需要子进程进行读管道的操作，所以先关掉1
            close(pipefd[1]);
            dup2(pipefd[0], 0);
            task();

            // 在当前进程退出之前，最好是把打开的文件描述符也关掉
            close(pipefd[0]);
            exit(0);
        }
        // 3. 构建一个channel名称
        std::string channel_name = "Channel-" + std::to_string(i);

        // 以下是父进程
        // 这里每次都需要父进程进行写管道的操作，所以先关掉0
        close(pipefd[0]);
        channels->push_back(Channel(pipefd[1], id, channel_name));
    }
}

int nextChannel(int channelnum)
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}

void sendTaskCommand(Channel &channel, int taskCommand)
{
    write(channel.getwfd(), &taskCommand, sizeof(taskCommand));
}

void ctrlProcessOnce(std::vector<Channel> &channels)
{
    sleep(1);
    //    step1: 选择一个任务
    int taskcommand = selectTask();
    //    step2: 选择一个管道和进程
    int channelindex = nextChannel(channels.size());
    //    step3: 发送任务
    sendTaskCommand(channels[channelindex], taskcommand);
    std::cout << std::endl;
    std::cout << "taskcommand: " << taskcommand << "channel: "
              << channels[channelindex].getname() << "sub process: " << channels[channelindex].getprocessid() << std::endl;
}

void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if (times > 0)
    {
        while (times--)
        {
            ctrlProcessOnce(channels);
        }
    }
    else
    {
        while (true)
        {
            ctrlProcessOnce(channels);
        }
    }
}

void cleanupChannel(std::vector<Channel> &channels)
{
    // int num = channels.size() - 1;
    // while (num >= 0)
    // {
    //     channels[num].closeChannel();
    //     channels[num--].wait();
    // }
    
    //值得注意的是：
    //  记：创建管道后创建子进程为一次操作，父进程为α，子进程依次为β1,β2,β3,β4,β5
    //      那么每次操作都会继承当前对应的父进程中struct file *fd_array[]这个数组
    //      第一次操作会先创建管道文件1，然后让α的fd_array数组中3, 4号元素分别以读, 写的方式指向这个管道1
    //      然后创建β1, β1会继承α, 
    //      β1先对α中的fd_array[]进行浅拷贝，β1中的fd_array数组中3, 4号元素也会分别以读, 写的方式指向这个管道1
    //      然后再修改，修改之后
    //      α读方式的3号元素 以及 β1写方式的4号元素对应的指针应被清空，保证管道的单侧写入，单侧读的特性
    //      α只剩4号元素指向管道1，β1只剩下3号元素指向管道1
    //      第二次操作仍会先创建新的管道文件2，然后让α的fd_array数组中3, 5号元素分别以读, 写的方式指向这个管道2
    //      注意：          此时α中有三个元素有值，分别是3，4，5号元素
    //      然后创建β2, β2会继承 α, 
    //      β2先对α中的fd_array[]进行浅拷贝，β2中的fd_array数组中3, 5号元素也会分别以读, 写的方式指向这个管道2
    //      注意：          β2继承之后，3, 5号元素指向管道2，β2的fd_array[]这个数组的4号元素会指向管道1，
    //          这就造成了  多个写入指针你指向同一个管道  这一现象
    //
    //  所以针对这一现象，
    //  如果要使用下面的循环，即关闭管道后，立马用父进程去等待子进程的结束
    //  只能针对 多次继承 进行新一轮的处理
    for (auto &channel : channels)
    {
        channel.closeChannel();
        channel.wait();
    }

    // for (auto &channel : channels)
    // {
    //     channel.closeChannel();
    // }

    // for (auto &channel : channels)
    // {
    //     channel.wait();
    // }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "processnum" << std::endl;
        return 1;
    }
    int num = std::stoi(argv[1]);
    loadTask();
    std::vector<Channel> channels;
    // 1. 创建管道, 用一个vector<Channel>管理管道，管道自动与当前循环到
    createchannelandsub(num, &channels, work);

    // 下面是测试创建管道和子进程成功
    //  for(auto& channel : channels)
    //  {
    //      std::cout << "========================" << std::endl;
    //      std::cout << channel.getname() << std::endl;
    //      std::cout << channel.getprocessid() << std::endl;
    //      std::cout << channel.getwfd() << std::endl;
    //  }

    // sleep(100);

    // 2. 通过channel控制子进程
    ctrlProcess(channels, 5);

    // 3. 回收管道和子进程，要做的工作有两个
    //    3.1 关闭所有的写端的进程，
    //    3.2 回收子进程
    cleanupChannel(channels);

    return 0;
}
